﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Toenda.Foundation.Checksum {
	/// <summary>
	/// Implements a double-add-double checksum method
	/// </summary>
	public class DoubleAddDouble {
		/// <summary>
		/// Computes a checknumber on base of the double-add-double method
		/// </summary>
		/// <param name="value">A value for the double-add-double method.</param>
		/// <param name="eachLetterForCalculation">Use each letter for the calculation or only letter-blocks.</param>
		/// <param name="checksuminValueIncluded">A value that indicates if the calculation should start with a big weight from the right side.</param>
		/// <returns>Returns a checksum</returns>
		public static int Compute(string value, bool eachLetterForCalculation, bool checksuminValueIncluded) {
			if(value == null) {
				throw new ArgumentNullException("The value parameter cannot be null!");
			}

			//if(checksuminValueIncluded) {
			//    value = value.Substring(0, value.Length - 1);
			//}

			int sum = 0;
			List<WeightDigit> list = new List<WeightDigit>();

			foreach(string item in value.ToCharacterList()) {
				if(item.IsAlpha()) {
					int letter = GetLetterNumber(item);

					if(eachLetterForCalculation) {
						foreach(char c in letter.ToString()) {
							list.Add(new WeightDigit() { Number = c.ToString().ToInt32() });
						}
					}
					else {
						list.Add(new WeightDigit() { Number = GetLetterNumber(item) });
					}
				}
				else if(item.IsNumeric()) {
					list.Add(new WeightDigit() { Number = item.ToInt32() });
				}
			}

			bool weight = checksuminValueIncluded;
			//bool weight = false;

			for(int i = (list.Count - 1); i >= 0; i--) {
				if(weight) {
					list[i].Weighting = 1;
					weight = false;
				}
				else {
					list[i].Weighting = 2;
					weight = true;
				}
			}

			List<int> result = new List<int>();

			foreach(WeightDigit d in list) {
				int number = d.Product;

				foreach(char c in number.ToString()) {
					result.Add(c.ToString().ToInt32());
				}
			}

			//int index = 0;

			foreach(int i in result) {
				//if(checksuminValueIncluded
				//&& index == result.Count - 1) {
				//    break;
				//}

				sum += i;
				//index++;
			}

			return (10 - (sum % 10)) % 10;
		}

		/// <summary>
		/// Returns a number that represents a specific letter for a double-add-double calculation
		/// </summary>
		/// <param name="letter">The letter.</param>
		/// <returns>Returns a number that represents a specific letter for a double-add-double calculation.</returns>
		public static int GetLetterNumber(string letter) {
			switch(letter.ToLower()) {
				case "a": return 10;
				case "b": return 11;
				case "c": return 12;
				case "d": return 13;
				case "e": return 14;
				case "f": return 15;
				case "g": return 16;
				case "h": return 17;
				case "i": return 18;
				case "j": return 19;
				case "k": return 20;
				case "l": return 21;
				case "m": return 22;
				case "n": return 23;
				case "o": return 24;
				case "p": return 25;
				case "q": return 26;
				case "r": return 27;
				case "s": return 28;
				case "t": return 29;
				case "u": return 30;
				case "v": return 31;
				case "w": return 32;
				case "x": return 33;
				case "y": return 34;
				case "z": return 35;
			}

			return -1;
		}

		/// <summary>
		/// Implements a weight digit of the double-add-doubel calculation
		/// </summary>
		public class WeightDigit {
			/// <summary>
			/// Gets or sets the number of a digit
			/// </summary>
			public int Number { get; set; }

			/// <summary>
			/// Gets or sets the weight of a digit
			/// </summary>
			public int Weighting { get; set; }

			/// <summary>
			/// Gets the product of the number and the weight
			/// </summary>
			public int Product {
				get {
					return Weighting * Number;
				}
			}

			/// <summary>
			/// Retusns a string that represents the current object
			/// </summary>
			/// <returns>Retusns a string that represents the current object</returns>
			public override string ToString() {
				StringBuilder str = new StringBuilder();

				str.Append("Number=");
				str.Append(this.Number);
				str.Append("; Weighting=");
				str.Append(this.Weighting);
				str.Append("; Product=");
				str.Append(this.Product);

				return str.ToString();
			}
		}
	}
}
